import math
import numpy as np
from PIL import Image, ImageFont, ImageDraw

from const.byte_order_enum import ByteOrder
from const.dot_matrix_style import DotMatrixStyle
from const.font_mode_enum import FontMode
from dto.param import Param


class Gen:

    def __init__(self, param: Param = None):
        self.param = param
        self.fontH = int(self.param.fontHeight)
        self.fontW = int(self.param.fontWidth)
        self.charsetStart = int(self.param.charsetStart, 16)
        self.charsetEnd = int(self.param.charsetEnd, 16)
        # big, small
        self.byteOrder = self.param.byteOrder
        # positive, negative
        self.dotMatrixStyle = self.param.dotMatrixStyle

        if self.param.dotMatrixMode == FontMode.V_FIRST_TOP_BOTTOM__LAST_LEFT_RIGHT.name.lower():
            self.do_gen_dot_matrix = v_ftb_llr
        elif self.param.dotMatrixMode == FontMode.V_FIRST_LEFT_RIGHT__LAST_TOP_BOTTOM.name.lower():
            self.do_gen_dot_matrix = v_flr_ltb
        elif self.param.dotMatrixMode == FontMode.H_FIRST_TOP_BOTTOM__LAST_LEFT_RIGHT.name.lower():
            self.do_gen_dot_matrix = h_ftb_llr
        elif self.param.dotMatrixMode == FontMode.H_FIRST_LEFT_RIGHT__LAST_TOP_BOTTOM.name.lower():
            self.do_gen_dot_matrix = h_flr_ltb

    def do_gen(self):
        image_font = ImageFont.truetype(self.param.font, size=(self.fontH - 1))
        # save the binary file
        with (open(get_binary_filename(self.param), 'wb') as f_binary):
            with (open(get_text_filename(self.param), 'w') as f_text):
                total_chr_size = self.charsetEnd - self.charsetStart + 1
                for c in range(self.charsetStart, self.charsetEnd + 1):
                    c_num = c
                    # invoke the callback to update progress bar etc.
                    self.param.callback(int((c_num - self.charsetStart + 1) * 101 / total_chr_size),
                                        f"{float((c_num - self.charsetStart + 1) / total_chr_size * 100):.2f}%")
                    c = self.check(c, self.param.charset)
                    if c == 0:
                        continue

                    # paint and write font
                    image = Image.new('L', (self.fontH, self.fontH), 0)
                    ImageDraw.Draw(image).text((0, 0), chr(c), font=image_font, fill=255)

                    # display the img by image.show(), save the image by image.save('c.png')
                    # convert to array from image
                    image_array = np.array(image)
                    image.close()

                    # total bytes of a font after converting to a dot matrix with font size
                    bytes_of_font = self.fontW * (ceil_division(self.fontH, 8))
                    # create array and clear
                    dot_matrix = np.zeros(bytes_of_font, dtype=np.uint8)

                    # generate the dot matrix
                    self.do_gen_dot_matrix(dot_matrix, self.fontH, self.fontW, image_array, self.byteOrder,
                                           self.dotMatrixStyle)

                    # write to file with binary
                    f_binary.write(bytes(dot_matrix))

                    # write to file with text
                    result = "{"
                    result += "".join("0x{:02X},".format(dot_matrix[i]) for i in range(bytes_of_font))
                    result += "}, "
                    result += ("// 0x{:02X}\n".format(c_num))
                    f_text.write(result)
                    # print(f"{"0x{:02X} - {:s}".format(c_num, chr(c))}")

    def check(self, c, charset):
        return c


def v_ftb_llr(dot_matrix, height, width, img_array, byte_order, dot_matrix_style):
    """
    Vertical(bit of byte), First top bottom, Last left right.

    |      | col0  | col1  | col2  | ... |
    | row0 | byte0 | byte3 | byte6 |     |                           | bit0
    | row1 | byte1 | byte4 | byte7 | ... | ---> Vertical bit of byte | bit1
    | row2 | byte2 | byte5 | byte8 |     |                           | bit2
    |  ... |                             |                           | ...
                                                                     | bit7
    :param dot_matrix:       result array
    :param height:           font height. 12, 16, 24
    :param width:            font width. 12, 16, 24
    :param img_array:        font image array
    :param byte_order:       small, low bit at first; big: high bit at first
    :param dot_matrix_style: positive, negative
    :return:                 None
    """
    bytes_of_height = ceil_division(height, 8)
    # traversal column
    for i in range(width):
        # traversal row
        for j in range(bytes_of_height):
            # How many bits does this byte occupy when fontSize % 8 != 0
            for bit_in_byte in range(8 if (j + 1) * 8 <= height else (height % 8)):
                bit = get_bit(img_array[j * 8 + bit_in_byte, i], byte_order, dot_matrix_style, bit_in_byte)
                if bit:
                    dot_matrix[bytes_of_height * i + j] |= bit


def v_flr_ltb(dot_matrix, height, width, img_array, byte_order, dot_matrix_style):
    """
    Vertical(bit of byte), First left right, Last top bottom.

    |      | col0  | col1  | col2  | ... |
    | row0 | byte0 | byte1 | byte2 |     |                           | bit0
    | row1 | byte3 | byte4 | byte5 | ... | ---> Vertical bit of byte | bit1
    | row2 | byte6 | byte7 | byte8 |     |                           | bit2
    |  ... |                             |                           | ...
                                                                     | bit7
    """
    bytes_of_height = ceil_division(height, 8)
    # traversal row
    for i in range(bytes_of_height):
        # traversal column
        for j in range(width):
            # How many bits does this byte occupy when fontSize % 8 != 0
            for bit_in_byte in range(8 if (i + 1) * 8 <= height else (height % 8)):
                bit = get_bit(img_array[i * 8 + bit_in_byte, j], byte_order, dot_matrix_style, bit_in_byte)
                if bit:
                    dot_matrix[bytes_of_height * i + j] |= bit


def h_ftb_llr(dot_matrix, height, width, img_array, byte_order, dot_matrix_style):
    """
    Horizontal(bit of byte), First top bottom, Last left right.

    |      | col0  | col1  | col2  | ... |
    | row0 | byte0 | byte3 | byte6 |     |
    | row1 | byte1 | byte4 | byte7 | ... | ---> Horizontal bit of byte {bit0 bit1 bit2 ... bit7}
    | row2 | byte2 | byte5 | byte8 |     |
    |  ... |                             |

    """
    bytes_of_width = ceil_division(width, 8)
    # column
    for i in range(bytes_of_width):
        # row
        for j in range(height):
            # How many bits does this byte occupy when fontSize % 8 != 0
            for bit_in_Byte in range(8 if (i + 1) * 8 <= width else (width % 8)):
                bit = get_bit(img_array[i * 8 + bit_in_Byte, j], byte_order, dot_matrix_style, bit_in_Byte)
                if bit:
                    dot_matrix[bytes_of_width * i + j] |= bit


def h_flr_ltb(dot_matrix, height, width, img_array, byte_order, dot_matrix_style):
    """
    Horizontal(bit of byte), First left right, Last top bottom.

    |      | col0  | col1  | col2  | ... |
    | row0 | byte0 | byte1 | byte2 |     |
    | row1 | byte3 | byte4 | byte5 | ... | ---> Horizontal bit of byte {bit0 bit1 bit2 ... bit7}
    | row2 | byte6 | byte7 | byte8 |     |
    |  ... |                             |

    """
    bytes_of_width = ceil_division(width, 8)
    # row
    for i in range(height):
        # column
        for j in range(bytes_of_width):
            # How many bits does this byte occupy when fontSize % 8 != 0
            for bit_in_Byte in range(8 if (j + 1) * 8 <= width else (width % 8)):
                bit = get_bit(img_array[j * 8 + bit_in_Byte, i], byte_order, dot_matrix_style, bit_in_Byte)
                if bit:
                    dot_matrix[bytes_of_width * i + j] |= bit


def get_bit(bit, byte_order, dot_matrix_style, k):
    # the current bit location in the two-dimensional array
    if dot_matrix_style == DotMatrixStyle.NEGATIVE.name.lower():
        bit = (0 if bit else 1)

    if bit:
        if byte_order == ByteOrder.SMALL.name.lower():
            return 1 << k
        else:
            return 1 << (8 - k - 1)
    else:
        return 0


def ceil_division(a, b):
    # 普通除法，结果为浮点数
    result = a / b
    return a // b if result.is_integer() else math.ceil(result)


def get_binary_filename(param: Param = None):
    # C:/gbk_12.ftl
    return param.location + '/' + param.charset + '_' + param.fontHeight + '.ftl'


def get_text_filename(param: Param = None):
    # C:/gbk_12.txt
    return param.location + '/' + param.charset + '_' + param.fontHeight + '.txt'


if __name__ == '__main__':
    pass
